package com.itqf.controller;

import com.itqf.bean.DtsAdmin;
import com.itqf.captcha.CaptchaCodeManager;
import com.itqf.dto.DtsAdminDto;
import com.itqf.service.DtsPermissionService;
import com.itqf.service.DtsRoleService;
import com.itqf.util.Base64;
import com.itqf.util.ResponseUtil;
import com.itqf.utils.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.*;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;

/**
 * projectName: sm_project
 *
 * @author: 赵伟风
 * time: 2021/7/13 9:39
 * description:后台认证模块接口:认证路径,验证码,登录,用户信息获取
 *
 * 6个接口  admin/auth
 *   3个接口前端需要的状态接口
 *   3个功能接口
 *       验证码图片
 *       登录功能
 *       用户信息
 */
@RestController
@CrossOrigin("*")
@RequestMapping("/admin/auth")
public class AdminAuthController {

    /**
     * 日志输出对象
     */
    public static final Logger logger = LoggerFactory.getLogger(AdminAuthController.class);

    @Autowired
    private DtsRoleService dtsRoleService;
    @Autowired
    private DtsPermissionService dtsPermissionService;
    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 认证失败
     * @return
     */
    @GetMapping("401")
    public Object ret401(){
        logger.info("[认证失败]接口调用!");
        return ResponseUtil.unlogin();
    }
    /**
     * 登录成功对应的跳转接口
     * @return
     */
    @GetMapping("index")
    public Object retIndex(){
        logger.info("[登录成功]触发跳转接口!");
        return ResponseUtil.ok();
    }

    /**
     * 授权失败
     * @return
     */
    @GetMapping("403")
    public Object ret403(){
        logger.info("[授权失败]接口调用!");
        return  ResponseUtil.unauthz();
    }




    /**
     * 生成验证码图片接口
     * 思路: 后台返回验证码图片和验证码图片的唯一标识!注意图片转成base64!
     *          1.验证码生成的工具  生成验证码  生成UUID
     *          2.缓存验证码工具
     *          3.base64的工具  Base64.encode([]xx);
     * @return
     */
    @GetMapping("captchaImage")
    public Object retCaptcha() throws IOException {

        logger.info("[验证生成]接口开始调用!");
        //1.生成验证码所需原材料 code  / uuid
        String code = VerifyCodeUtils.generateVerifyCode(4);
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");

        logger.debug("[验证生成] code值为:{}",code);

        /**
         * slf4j的日志输出工具类中,可以快速进行值替换!
         *    只需要在替换的值上写{}占位符,在方法后的参数按照位置添加要显示的数据即可!
         */
        logger.debug("[验证生成]验证码为:{},uuid为:{}",code,uuid);

        //2.将验证码封装成CaptchaItem并存储到CaptchaManage中
        boolean cache = CaptchaCodeManager.addToCache(uuid, code, 20);

        if (!cache) {
            //验证码存储失败
            logger.error("[验证生成]生成的验证码:{},已经在存在,请稍后再试!", code);
            //程序终止

            /**
             * ResponseUtils 简单的认证的失败的错误状态
             * AdminResponseCode 其他异常的错误枚举类型
             * AdminResponseUtil 更加便捷的调用AdminResponseCode生成的错误归结!
             * ResponseUtil.fail(AdminResponseCode.AUTH_CAPTCHA_ERROR.code(),AdminResponseCode.AUTH_CAPTCHA_ERROR.desc()) ;
             */
            return AdminResponseUtil.fail(AdminResponseCode.AUTH_CAPTCHA_ERROR);
        }

        //3.生成验证码图片对应的Base64
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            VerifyCodeUtils.outputImage(110, 36, baos, code);
        } catch (IOException e) {
            logger.error("[验证生成]验证码生成图片失败!");
            throw e;
        }

        String imgStr = Base64.encode(baos.toByteArray());


        //4.生成返回的结果数据
        Map data = new HashMap();
        data.put("img", imgStr);
        data.put("uuid", uuid);;

        logger.info("[验证生成]接口调用结束,返回数据为:{}!",data);
        return ResponseUtil.ok(data);
    }


    /**
     *   /**
     *          *
     *          *  bean
     *          *    实体类 学生 Student
     *          *    int
     *          *    原始数据类型[基本数据类型]
     *          *
     *          *  pojo
     *          *    实体类 持久化 数据库挂钩  学生表  - 学生类 pojo
     *          *    Integer null 包装类 [引用数据类型 ]
     *          *
     *          *  vo
     *          *    View Object视图对象 返回给前端!
     *          *    为什么要Vo!
     *          *    因为实体类无法满足前端的显示需求!
     *          *    User
     *          *    code:0 msg:ok data:user  ResultVo  R
     *          *  dto
     *          *    data transfer object 数据传输对象!
     *          *    dto
     *          *    controller(username,password,code,uuid) - obj -  service (username,password,code,uuid) - obj - mapper (@Param("username")username,password,code,uuid)
     *
     *
     * @param dtsAdminDto
     * @return
     */
    @PostMapping(value = "login",consumes = "application/json;charset=utf-8")
    public Object login(@RequestBody DtsAdminDto dtsAdminDto){
        logger.info("[用户登录]登录接口开始调用!");
        logger.debug("[用户登录]用户输入的信息为:{}", dtsAdminDto);
        //1.判断参数

        if (dtsAdminDto == null || StringUtils.isEmpty(dtsAdminDto.getUsername()) || StringUtils.isEmpty(dtsAdminDto.getPassword())
            ||StringUtils.isEmpty(dtsAdminDto.getCode()) || StringUtils.isEmpty(dtsAdminDto.getUuid()))
        {
            logger.warn("[用户登录]登录的数据有为null的!");
            //参数异常
            return ResponseUtil.badArgument();
        }
        //2.判断验证
        //获取缓存的验证码
        String code = CaptchaCodeManager.getCachedCaptcha(dtsAdminDto.getUuid());

        if (code == null) {
            logger.warn("[用户登录]本地缓存验证码失效!");
            /**
             * ResponseUtils 认证的状态码
             * AdminResponseCode
             * AdminResponseUtil
             * 返回验证码失效!
             */
            return AdminResponseUtil.fail(AdminResponseCode.AUTH_CAPTCHA_EXPIRED);
        }

        if(!code.equalsIgnoreCase(dtsAdminDto.getCode()))
        {
            logger.warn("[用户登录]验证码校验失败!用户输入:{},正确的是:{}",dtsAdminDto.getCode(),code);
            return AdminResponseUtil.fail(AdminResponseCode.AUTH_CAPTCHA_ERROR);
        }

        //3.认证过程
        //封装toke
        UsernamePasswordToken token = new UsernamePasswordToken(dtsAdminDto.getUsername(), dtsAdminDto.getPassword());
        //subject认证login
        Subject subject = SecurityUtils.getSubject();

        try {
            subject.login(token);
        }catch (AuthenticationException e)
        {
            logger.error("[用户登录]用户输入的账号或者密码错误!");
            return AdminResponseUtil.fail(AdminResponseCode.ADMIN_INVALID_ACCOUNT_OR_PASSWORD);
        }

        //4.返回认证结果
        //获取用户的唯一标识
        Serializable id = subject.getSession().getId();

        return ResponseUtil.ok(id);
    }

    /**
     * 获取用户信息接口
     * @return
     */
    //shiro需要认证
    @RequiresAuthentication
    @GetMapping("info")
    public Object info(){

        System.out.println("AdminAuthController.info");

        //1.获取dtsadmin
        Subject subject = SecurityUtils.getSubject();
        DtsAdmin dtsAdmin = (DtsAdmin) subject.getPrincipal();


        logger.debug("[用户信息]读取缓存的dtsAdmin对象为:{}", dtsAdmin);


        //2.数据封装
        Map data = new HashMap();
        data.put("name", dtsAdmin.getUsername());
        data.put("avatar",dtsAdmin.getAvatar());


        //3.业务调用
        //查询角色
        List<String> roleNames = dtsRoleService.queryRoleNames(dtsAdmin.getRoleIds());

        if (roleNames == null) {
            logger.error("[用户信息]用户没有分配角色!");
            //无操作权限
            return ResponseUtil.unauthz();
        }

        //查询数据库有的权限
        /**
         * 你的权限 admin:role:list
         */
        Set<String> permissionNames = dtsPermissionService.queryPermissionNames(dtsAdmin.getRoleIds());

        //4.继续封装
        data.put("roles", roleNames);

        /**
         * 如果你的权限中有* 返回就是 *
         * 如果你的权限中没有* 返回你拥有权限的接口 请求方式  路径
         */
        /**
         * 参数1:要ioc容器  ApplicationContext
         * 参数2:具体的包
         */
        String basePackage = "com.itqf.controller";


        /**
         * 项目所以权限
         * post url
         */
        List<Permission> permissionList = PermissionUtil.listPermission(applicationContext, basePackage);

        //处理一下放入map方便查询
        Map map = new HashMap();

        for (Permission permission : permissionList) {
            //将权限作为key 路径作为值
            map.put(permission.getRequiresPermissions().value()[0],permission.getApi());
        }


        // ["post url"] 容器
        Set<String> urls = new HashSet<>();



        //遍历我们拥有的权限名字
        for (String name : permissionNames) {

            //检查我们用*权限!
            if (name.equalsIgnoreCase("*"))
            {
              //我们拥有全部的权限
                urls.clear();
                urls.add("*");
                break;
            }

            //检查项目是否拥有你权限的功能,有添加urls
            if (map.containsKey(name))
            {
                urls.add((String) map.get(name));
            }
        }

        logger.debug("[用户信息]获取到的用户权限地址为:{}", urls);


        data.put("perms", urls);

        return ResponseUtil.ok(data);
    }


    /**
     * 退出登录
     * @return
     */
    @RequiresAuthentication
    @PostMapping("logout")
    public Object logout(){

        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        logger.info("[退出登录]退出登录接口调用完毕!");

        return ResponseUtil.ok();
    }


}
